﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Xml;
using System.IO;

namespace Luna.Framework.Manifest {

	public interface IManifestManager: IContextAwareObject  {
		void loadManifest(string defaultManifestFile);
		void registerManifest(IConfigurationManifest manifest);
		void registerProcessor(IManifestProcessor processor);
		IEnumerable<IManifestProcessor> getProcessor(IConfigurationManifest manifest, Type hostManifestType);

		IEnumerable<IConfigurationManifest> getManifests(Predicate<IConfigurationManifest> filter);
		T applyManifest<T>(IConfigurationManifest manifest, IAppContext context, T target = null) where T : class;

		string lookupString(string key);
	}

	public class ManifestManager: IManifestManager {

		private HashSet<IConfigurationManifest> manifestRegistry;
		private List<IManifestProcessor> processorRegistry;

		private class ManifestComparer : EqualityComparer<IConfigurationManifest> {

			public override bool Equals(IConfigurationManifest x, IConfigurationManifest y) {
				bool result = !((x != null) ^ (y != null));
				if (x != null && result)
					result = x.isEquivalent(y);
				return result;
			}

			public override int GetHashCode(IConfigurationManifest obj) {
				if (obj is XmlManifest)
					return (obj as XmlManifest).Filename.GetHashCode();
				return obj.GetHashCode();
			}
		}

		public ManifestManager() {
			
			manifestRegistry = new HashSet<IConfigurationManifest>(new ManifestComparer());
			processorRegistry = new List<IManifestProcessor>();

			processorRegistry.Add(new PropertyManifestProcessor());
			processorRegistry.Add(new ListPropertyManifestProcessor());
			processorRegistry.Add(new DictionaryPropertyManifestProcessor());
			processorRegistry.Add(new ServiceManifestProcessor());
			processorRegistry.Add(new BuilderManifestProcessor());
			processorRegistry.Add(new PluginManifestProcessor());
		}

		public IList<IManifestProcessor> Processors {
			get { return processorRegistry; }
		}

		public void registerManifest(IConfigurationManifest manifest) {
			if (!manifestRegistry.Contains(manifest))
				manifestRegistry.Add(manifest);
		}

		public void loadManifest(string filename) {
			XmlDocument doc = new XmlDocument();
			doc.Load(filename);
			if (doc.LastChild.Name.Equals("manifest")) {
				IConfigurationManifest manifest = new XmlManifest(doc.LastChild, this) { Filename = Path.GetFullPath(filename) };
				registerManifest(manifest);
			}
			else
				throw new ApplicationException(string.Format("Manifest file signature is not correct.\r\n{0}", filename));
		}

		public IEnumerable<IConfigurationManifest> getManifests(Predicate<IConfigurationManifest> filter) {
			foreach(IConfigurationManifest manifest in manifestRegistry)
				foreach (IConfigurationManifest child in manifest.getChildren())
					if (filter(child)) yield return child;
				}

		/// <summary>
		/// Apply an manifest template to an target. If the given target is missing (null), new object conformed
		/// with then given manifest is created.
		/// </summary>
		/// <param name="context">The reference context of given target</param>
		/// <param name="manifest"></param>
		/// <param name="target"></param>
		/// <returns></returns>
		public T applyManifest<T>(IConfigurationManifest manifest, IAppContext context, T target = null) where T: class {
			if (target == null) {
				Type targetType = manifest.Type != null ? manifest.Type : typeof(T);
				//target = Activator.CreateInstance(targetType) as T;
				ConstructorInfo ctor = targetType.GetConstructor(new Type[] { typeof(IAppContext) });
				if (ctor != null)
					target = (T)ctor.Invoke(new object[] { context });
				else {
					target = (T)Activator.CreateInstance(targetType);
					target.setPropertyValue("Context", context, null);
				}
			}

			if (target != null) {

				if (manifest is XmlManifestItem) {
					foreach (string key in (manifest as XmlManifestItem).getPropertyNames()) {
						if (char.IsUpper(key[0]))
							PropertyManifestProcessor.setPropertyValue(target, key, manifest.getProperty<string>(key));
					}
				}

				if (manifest.hasChild()) {
					IAppContext targetContext = target.extractAppContext();
					if (targetContext == null)
						targetContext = context;

					foreach (IConfigurationManifest child in manifest.getChildren()) {
						this.processManifest(child, target, targetContext);
					}
				}

				string name = target.getPropertyValue<string>("Name", null);
				if (name.isNotNullAndEmpty())
					context[name] = target;
			}			
			return target;
		}

		//public object resolveValue(IManifest manifest, string value, IAppContext context) {
		//    if (value.StartsWith("@@"))
		//        return value.Substring(1);
		//    if (value.StartsWith("@"))
		//        return new Uri(value.Substring(1)).resolveObject<object>(context);
		//    return value;
		//}

		public IAppContext Context { get; set; }

		public virtual void registerProcessor(IManifestProcessor processor) {
			processorRegistry.Add(processor);
		}

		public virtual IEnumerable<IManifestProcessor> getProcessor(IConfigurationManifest manifest, Type hostManifestType) {
			for (int i = processorRegistry.Count - 1; i >= 0; i--)
				if (processorRegistry[i].isSupport(manifest, hostManifestType))
					yield return processorRegistry[i];
		}

		public string lookupString(string key) {
			foreach(IConfigurationManifest manifest in manifestRegistry)
				if (manifest is XmlManifest && (manifest as XmlManifest).Strings.ContainsKey(key)) 
					return (manifest as XmlManifest).Strings[key];
			return "";
		}
	}
}

namespace Luna {
	using Luna.Framework;
	using Luna.Framework.Manifest;

	public static class ManifestExtension {
		public static IConfigurationManifest getManifest(this IManifestManager manager, string id) {
			return manager.getManifests(p => p.getProperty<string>("id") == id).FirstOrDefault();
		}

		public static IConfigurationManifest getManifest(this IManifestManager manager, string name, string id) {
			return manager.getManifests(p => p.getProperty<string>("id") == id && p.Name == name).FirstOrDefault();
		}

		public static IConfigurationManifest getManifest(this IManifestManager manager, Predicate<IConfigurationManifest> pred) {
			return manager.getManifests(pred).FirstOrDefault();
		}

		public static IEnumerable<IConfigurationManifest> getManifests(this IManifestManager manager, string name) {
			return manager.getManifests(p => p.Name == name);
		}

		public static void processManifest(this IManifestManager manager, IConfigurationManifest manifest, object target, IAppContext context) {
			object handler = manager.getProcessor(manifest, target.GetType());
			if (handler is IManifestProcessor)
				(handler as IManifestProcessor).process(manager, manifest, target, context);
			else if (handler is IEnumerable<IManifestProcessor>)
				foreach (IManifestProcessor pro in (handler as IEnumerable<IManifestProcessor>))
					if (pro.process(manager, manifest, target, context)) break;
		}
	}
}